﻿namespace JsViewEngine
{
    using System;
    using System.Collections.Concurrent;
    using System.IO;
    using System.Security.Cryptography;
    using System.Text;
    using System.Threading;
    using System.Web.Mvc;

    public class JsTemplateView : IView
    {
        private readonly string jsrenderPath;
        private readonly string viewconvertersPath;

        private static readonly ConcurrentDictionary<JurassicEngineInstanceOptions, ThreadLocal<JurassicEngineInstance>> JurassicScriptEngines =
            new ConcurrentDictionary<JurassicEngineInstanceOptions, ThreadLocal<JurassicEngineInstance>>();

        private readonly string templateText;
        private readonly string templateFileHash;

        public JsTemplateView(string physicalPath, string jsrenderPath, string viewconvertersPath)
        {
            if (string.IsNullOrEmpty(physicalPath))
            {
                throw new ArgumentNullException("physicalPath");
            }

            if (!File.Exists(jsrenderPath))
            {
                throw new ArgumentException(string.Format("Path to jsrender.js does not exist: \"{0}\".", jsrenderPath), "jsrenderPath");
            }

            // load file
            this.templateText = File.ReadAllText(physicalPath);
            this.templateFileHash = this.ComputeHashString(this.templateText);

            this.jsrenderPath = jsrenderPath;

            if (string.IsNullOrWhiteSpace(viewconvertersPath) == false && File.Exists(viewconvertersPath))
            {
                this.viewconvertersPath = viewconvertersPath;
            }
        }

        public string ComputeHashString(string text)
        {
            SHA1 sha = new SHA1CryptoServiceProvider();
            // This is one implementation of the abstract class SHA1.
            var result = sha.ComputeHash(Encoding.UTF8.GetBytes(text));

            return BitConverter.ToString(result).Replace("-", "").ToLower();
        }

        private JurassicEngineInstance GetScriptEngineInstance()
        {
            var instanceOptions = new JurassicEngineInstanceOptions(this.jsrenderPath, this.viewconvertersPath);
            var scriptEngineThreadLocal = JurassicScriptEngines.GetOrAdd(
                instanceOptions,
                s => new ThreadLocal<JurassicEngineInstance>(
                    () => new JurassicEngineInstance(instanceOptions)));

            return scriptEngineThreadLocal.Value;
        }

        /// <summary>
        /// Renders the specified view context by using the specified the writer object.
        /// </summary>
        /// <param name="viewContext">The view context.</param><param name="writer">The writer object.</param>
        public void Render(ViewContext viewContext, TextWriter writer)
        {
            // Perform replacement
            var engineInstance = this.GetScriptEngineInstance();
            string parsedContents = engineInstance.Render(
                this.templateFileHash, this.templateText, viewContext.ViewData.Model, null);

            writer.Write(parsedContents);
        }
    }
}